﻿using System.Reflection;
using System.Runtime.InteropServices;

namespace System.NETool;

/// <summary>
/// 定义结构文件接口。
/// </summary>
public interface IStructFile : IConfigFile
{
    /// <summary>
    /// 初始化结构文件。
    /// </summary>
    public void Init();
}

/// <summary>
/// 提供结构体相关的扩展方法。
/// </summary>
public static class StructEx
{
    /// <summary>
    /// 结构序列化字符串
    /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    /// </summary>
    public const string StructLayoutString = "[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]";

    /// <summary>
    /// 结构序列化字符串数组长度
    /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
    /// </summary>
    public const string StructMarshalAs = "[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]";

    /// <summary>
    /// 将字节数组转换为值类型的对象。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="this">字节数组</param>
    /// <returns>对象</returns>
    public static T ToStruct<T>(this byte[] @this) where T : struct
    {
        if (@this == null) throw new NullReferenceException();
        if (@this.Length == 0) throw new ArgumentNullException(nameof(@this));

        // 得到结构体的大小
        int size;
        try
        {
            size = Marshal.SizeOf<T>();
        }
        catch (Exception)
        {
            throw new NullReferenceException();
        }

        if (size > @this.Length)
        {
            throw new NullReferenceException();
        }

        // 分配结构体大小的内存空间
        var structPtr = Marshal.AllocHGlobal(size);
        // 将 byte 数组拷到分配好的内存空间
        Marshal.Copy(@this, 0, structPtr, size);
        // 将内存空间转换为目标结构体
        var obj = Marshal.PtrToStructure<T>(structPtr);
        // 释放内存空间
        Marshal.FreeHGlobal(structPtr);
        // 返回结构体
        return obj;
    }

    /// <summary>
    /// 获取值类型对象的大小。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <returns>对象的大小</returns>
    public static int SizeOf<T>() where T : struct => Marshal.SizeOf<T>();

    /// <summary>
    /// 将值类型的对象转换为字节数组。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="this">对象</param>
    /// <returns>字节数组</returns>
    public static byte[] ToByteArray<T>(this T @this) where T : struct
    {
        // 得到结构体的大小
        var size = Marshal.SizeOf<T>();
        // 创建 byte 数组
        var array = new byte[size];
        // 分配结构体大小的内存空间
        var structPtr = Marshal.AllocHGlobal(size);
        // 将结构体拷到分配好的内存空间
        Marshal.StructureToPtr(@this, structPtr, false);
        // 从内存空间拷到 byte 数组
        Marshal.Copy(structPtr, array, 0, size);
        // 释放内存空间
        Marshal.FreeHGlobal(structPtr);
        // 返回 byte 数组
        return array;
    }

    /// <summary>
    /// 从文件加载结构体对象。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="obj">对象</param>
    /// <param name="dir">目录</param>
    /// <param name="fileName">文件名</param>
    /// <returns>是否成功加载</returns>
    public static bool Load<T>(this ref T obj, string dir, string fileName = "") where T : struct, IStructFile
    {
        if (!Path.IsPathRooted(dir))
        {
            Console.WriteLine("指定路径" + dir + "不包含根路径。");
            throw new ArgumentException("不包含根路径。", nameof(dir));
        }

        var file = fileName.IsValid() ? fileName : Dir.VerifyFileName(obj.FileName);
        if (File.Exists(dir.DealPath() + file))
        {
            try
            {
                byte[] bts = File.ReadAllBytes(dir.DealPath() + file);
                obj = bts.ToStruct<T>();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(file + "加载失败，" + ex.Message);
            }
        }
        else
        {
            Console.WriteLine(file + "加载失败，数据文件不存在。");
        }

        return false;
    }

    /// <summary>
    /// 将结构体对象保存到文件。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="obj">对象</param>
    /// <param name="dir">目录</param>
    /// <param name="fileName">文件名</param>
    /// <returns>是否成功保存</returns>
    public static bool Save<T>(this T obj, string dir, string fileName = "") where T : struct, IStructFile
    {
        if (!Path.IsPathRooted(dir))
        {
            throw new ArgumentException("不包含根路径。", nameof(dir));
        }

        if (!Dir.CreateDir(dir))
        {
            throw new ArgumentNullException(dir, "路径不存在。");
        }

        string file = fileName.IsValid() ? fileName : Dir.VerifyFileName(obj.FileName);
        if (file.IsNullOrEmpty())
        {
            throw new ArgumentNullException(file, "文件名不能为空。");
        }

        string outfile = dir.DealPath() + file;
        if (File.Exists(outfile))
        {
            try
            {
                File.Delete(outfile);
            }
            catch
            {
                Console.WriteLine(outfile + " 已存在，无法继续保存。");
                return false;
            }
        }

        try
        {
            var array = obj.ToByteArray();
            using var fs = new FileStream(outfile, FileMode.Create);
            fs.Write(array, 0, array.Length);
            fs.Flush();
            fs.Close();

            return File.ReadAllBytes(outfile).SequenceEqual(array);
        }
        catch (Exception ex)
        {
            Console.WriteLine(outfile + " 保存失败。" + ex.Message);
            return false;
        }
    }

    /// <summary>
    /// 克隆结构体对象。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <param name="this">对象</param>
    /// <returns>克隆的对象</returns>
    public static T Clone<T>(this T @this) where T : struct => @this.ToByteArray().ToStruct<T>();

    /// <summary>
    /// 打印结构体的字段信息。
    /// </summary>
    /// <typeparam name="T">值类型</typeparam>
    /// <returns>结构体的字段信息字符串</returns>
    public static string PrintStruct<T>() where T : struct
    {
        var structType = typeof(T);
        var fields = structType.GetFields(BindingFlags.Public | BindingFlags.Instance);
        var structSize = Marshal.SizeOf<T>();
        var list = new List<StructField>();
        var lastOffset = 0;
        var lastField = "";
        var lastType = "";
        int size;
        foreach (var field in fields)
        {
            var offset = Marshal.OffsetOf(structType, field.Name).ToInt32();
            size = offset - lastOffset;
            if (size > 0) list.Add(new StructField(lastField, lastType, lastOffset, size));
            lastOffset = offset;
            lastField = field.Name;
            lastType = field.FieldType.Name;
        }

        size = structSize - lastOffset;
        if (size > 0)
        {
            list.Add(new StructField(lastField, lastType, lastOffset, size));
        }

        var fieldSize = 5;
        var fieldTypeSize = 4;
        foreach (var item in list)
        {
            fieldSize = Math.Max(fieldSize, item.Field.Length);
            fieldTypeSize = Math.Max(fieldTypeSize, item.Type.Length);
        }

        fieldSize = (fieldSize / 4 + 2) * 4;
        fieldTypeSize = (fieldTypeSize / 4 + 2) * 4;
        var otherSize = 12;
        var all = fieldSize + fieldTypeSize + otherSize + otherSize;
        using var sb = new ValueStringBuilder(1024);

        sb.AppendLine("//-" + "-".Repeat(all));
        sb.AppendLine("// " + $"Struct Name: {structType.Name}");
        sb.AppendLine("// " + $"Field Count: {fields.Length}");
        sb.AppendLine("// " + $"Total Size: {Marshal.SizeOf(structType)} bytes");

        sb.AppendLine("//-" + "-".Repeat(all));
        if (fields.Length > 0)
        {
            sb.AppendLine("// " + "Field".PadRight(fieldSize) + "Type".PadRight(fieldTypeSize) + "Offset".PadRight(otherSize) + "Size".PadRight(otherSize));
            sb.AppendLine("//-" + "-".Repeat(all));
            foreach (var item in list)
            {
                sb.AppendLine("// " + item.Field.PadRight(fieldSize) + item.Type.PadRight(fieldTypeSize) + item.Offset.ToString().PadRight(otherSize) + item.Size.ToString().PadRight(otherSize));
            }

            sb.AppendLine("//-" + "-".Repeat(all));
        }

        list.Clear();
        return sb.ToString();
    }

    /// <summary>
    /// 表示结构体字段的信息。
    /// </summary>
    /// <param name="field">字段名称</param>
    /// <param name="type">字段类型</param>
    /// <param name="offset">字段偏移量</param>
    /// <param name="size">字段大小</param>
    internal readonly struct StructField(string field, string type, int offset, int size)
    {
        public string Field => field;

        public string Type => type;

        public int Offset => offset;

        public int Size => size;
    }
}
